package com.ayu.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import com.ayu.shortlink.admin.common.biz.user.UserContext;
import com.ayu.shortlink.admin.common.biz.user.UserInfoDTO;
import com.ayu.shortlink.admin.common.constant.RedisCacheConstant;
import com.ayu.shortlink.admin.common.constant.UserConstant;
import com.ayu.shortlink.admin.common.convention.exception.ClientException;
import com.ayu.shortlink.admin.common.enums.UserErrorCodeEnum;
import com.ayu.shortlink.admin.dao.entiry.UserDO;
import com.ayu.shortlink.admin.dao.mapper.UserMapper;
import com.ayu.shortlink.admin.dto.req.ShortLinkGroupAddReqDTO;
import com.ayu.shortlink.admin.dto.req.UserLoginReqDTO;
import com.ayu.shortlink.admin.dto.req.UserRegisterReqDTO;
import com.ayu.shortlink.admin.dto.req.UserUpdateReqDTO;
import com.ayu.shortlink.admin.dto.resp.UserLoginRespDTO;
import com.ayu.shortlink.admin.dto.resp.UserRespDTO;
import com.ayu.shortlink.admin.service.GroupService;
import com.ayu.shortlink.admin.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate stringRedisTemplate;

    private final GroupService groupService;
    @Override
    public UserRespDTO getUserByUsername(String username) {
        UserDO userDO = lambdaQuery().eq(UserDO::getUsername, username).one();

        if(userDO == null){
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        return BeanUtil.copyProperties(userDO, UserRespDTO.class);

    }

    @Override
    public boolean hasUsername(String username) {
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(UserRegisterReqDTO requestPram) {
        //查询当前用户名是否创建
        if(userRegisterCachePenetrationBloomFilter.contains(requestPram.getUsername())){
            throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);
        }
        //并将常量+用户名作为分布式锁，避免大量用户在毫秒值的时间内去注册用户
        RLock lock = redissonClient.getLock(RedisCacheConstant.LOCK_USER_REGISTER_KEY +requestPram.getUsername());
        //如果获取不到锁，证明有人正在注册该用户名
        if(!lock.tryLock()){
            throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);
        }

        try {
            //进行用户注册
            int insert = baseMapper.insert(BeanUtil.toBean(requestPram, UserDO.class));
            if(insert < 1){
                //注册失败 抛出异常
                throw new ClientException(UserErrorCodeEnum.USER_SAVE_ERROR);
            }
            //将用户添加至默认分组
            groupService.addGroup(requestPram.getUsername(), ShortLinkGroupAddReqDTO
                    .builder().name("默认分组").build());
            //注册成功 添加到布隆过滤器
            userRegisterCachePenetrationBloomFilter.add(requestPram.getUsername());

        }catch (DuplicateKeyException exception){
            //注册失败 抛出异常
            throw new ClientException(UserErrorCodeEnum.USER_EXIST);
        }finally {
            lock.unlock();
        }
    }

    @Override
    public void update(UserUpdateReqDTO requestPram) {
        //进行用户验证
        if(!Objects.equals(requestPram.getUsername(), UserContext.getUsername())){
            throw new ClientException("当前登录用户修改请求异常");
        }

        lambdaUpdate()
                .eq(UserDO::getUsername,requestPram.getUsername())
                .update(BeanUtil.toBean(requestPram,UserDO.class));
    }

    @Override
    public UserLoginRespDTO login(UserLoginReqDTO requestPram) {
        //先用布隆过滤器防止恶意访问
        if(!hasUsername(requestPram.getUsername())){
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }

        UserDO user = lambdaQuery()
                .eq(UserDO::getUsername, requestPram.getUsername())
                .eq(UserDO::getPassword, requestPram.getPassword())
                .one();
        if(user == null){
            throw new ClientException(UserErrorCodeEnum.USER_LOGIN_ERROR);
        }

        //判断是否是已经登录的 (用户的token是否存在)
        Map<Object, Object> hasLoginMap = stringRedisTemplate.opsForHash().entries(UserConstant.USER_HAS_LOGIN + requestPram.getUsername());
        if(CollUtil.isNotEmpty(hasLoginMap)){
            stringRedisTemplate.expire(UserConstant.USER_HAS_LOGIN + requestPram.getUsername(), 30L, TimeUnit.MINUTES);
            String token = hasLoginMap.keySet().stream()
                    .findFirst()
                    .map(Object::toString)
                    .orElseThrow(() -> new ClientException("用户登录错误"));
            return new UserLoginRespDTO(token);
        }

        String uuid = UUID.randomUUID().toString();
        UserInfoDTO infoDTO = BeanUtil.toBean(user, UserInfoDTO.class);
        infoDTO.setUserId(String.valueOf(user.getId()));

        stringRedisTemplate.opsForHash().put(UserConstant.USER_HAS_LOGIN+requestPram.getUsername(),uuid, JSON.toJSONString(infoDTO));
        //设置过期时间 30天
        stringRedisTemplate.expire(UserConstant.USER_HAS_LOGIN+requestPram.getUsername(),30L, TimeUnit.MINUTES);
        return new UserLoginRespDTO(uuid);
    }

    @Override
    public Boolean hasLogin(String username,String token) {
        return stringRedisTemplate.opsForHash().get(UserConstant.USER_HAS_LOGIN+username,token) != null;
    }

    @Override
    public void logout(String username,String token) {
         if(hasLogin(username,token)){
             stringRedisTemplate.delete(UserConstant.USER_HAS_LOGIN+username);
             return;
         }
         throw new ClientException(UserErrorCodeEnum.USER_NOT_LOGIN);
    }


}
